#include<stdio.h>
#include<sys/types.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<mysql/mysql.h>
#include <sys/epoll.h>

#define PORT 55555
#define IP "192.168.42.128"

//存储用户信息的结构体
struct User{
    char name[50];      //用户名
    char account[20];   //账号
    char password[20];  //密码 
    int flag;           //用来服务器的功能判断  1、注册  2、登录  3、注销
};

//收发信息的结构体
struct Infor{
	char faname[50];    //发送方用户名
	char jiename[50];     //接收方用户名
	char buff[1024];    //发送的消息
    int flag;           //用于功能的选择    1、私聊  2、群聊
};

//用于判断登录信息
struct User_name{  
	char name[50];
	int flag;
};

//储存客户端的信息
struct cliname{  
	int cid;
	char name[50];
};
struct cliname clin[20];

char query[255]={0};//用于储存sql语句
int serfd,clifd,read_num;
struct sockaddr_in seraddr; 
int clinum=0;
 
MYSQL ret;
MYSQL_RES * res=NULL;
MYSQL_ROW myrow;

//连接数据库
MYSQL *connectdata();
void login_ver(MYSQL *data);
//注册
void regist(MYSQL *data,struct User user,int m_cd,struct sockaddr_in cli_addr);
//登录
void login(MYSQL *data,struct User user,int m_cd,struct sockaddr_in cli_addr);
//注销
void logout(MYSQL *data,struct User user,int m_cd);
//私聊
void prichat(struct Infor outfor,int m_cd);
//群聊
void grochat(struct Infor outfor,int m_cd);

/************************************************************************************ */
int main(){
	MYSQL *data=NULL;
	data=connectdata();
	login_ver(data);	
	close(serfd);
	return 0;
}
/************************************************************************************* */

//连接数据库
MYSQL *connectdata(){
	MYSQL *data=NULL;
	data=mysql_init(&ret);
	data=mysql_real_connect(data,"localhost","root","12345678","wechat",0,0,0);
    if(data){
        printf("数据库连接成功!\n");
    }else{
        printf("数据库连接失败!\n");
    }
	return data;
}

//注册
void regist(MYSQL *data,struct User user,int m_cd,struct sockaddr_in cli_addr){
	memset(query,0,sizeof(query));
	sprintf(query,"select * from users where name='%s' or account='%s';",user.name,user.account);
	mysql_query(data,query);
	res=mysql_store_result(data);
	if(mysql_num_rows(res)==0){
		mysql_free_result(res);
		memset(query,0,sizeof(query));
		sprintf(query,"insert into users(name,account,password,ip) values('%s','%s','%s','%s');",user.name,\
		        user.account,user.password,inet_ntoa(cli_addr.sin_addr));
		mysql_query(data,query);
		res=mysql_store_result(data);
		mysql_free_result(res);
		write(m_cd,"1",2);
	}else{
		write(m_cd,"0",2);
	}
	
}

//登录
void login(MYSQL *data,struct User user,int m_cd,struct sockaddr_in cli_addr){
	struct User_name u_name;
	memset(query,0,sizeof(query));
	sprintf(query,"select name from users where account='%s' and password='%s';",user.account,user.password);
	mysql_query(data,query);
	res=mysql_store_result(data);
	if(mysql_num_rows(res)==0){
		u_name.flag=0;
		mysql_free_result(res);
		write(m_cd,&u_name,sizeof(u_name));
	}else{
		myrow=mysql_fetch_row(res);
		sprintf(u_name.name,"%s",myrow[0]);
		for(int m=0;m<clinum;m++){
			if(clin[m].cid==m_cd){
				sprintf(clin[m].name,"%s",u_name.name);
			}
		}
		u_name.flag=1;
		mysql_free_result(res);
		write(m_cd,&u_name,sizeof(u_name));
		memset(query,0,sizeof(query));
		sprintf(query,"update users set ip='%s' where account='%s' and password='%s';",\
		inet_ntoa(cli_addr.sin_addr),user.account,user.password);
		mysql_query(data,query);
	}
}

//注销
void logout(MYSQL *data,struct User user,int m_cd){
	memset(query,0,sizeof(query));
	sprintf(query,"select name from users where account='%s' and password='%s';",user.account,user.password);
	mysql_query(data,query);
	res=mysql_store_result(data);
	if(mysql_num_rows(res)==0){
		mysql_free_result(res);
		write(m_cd,"0",2);
	}else{	
		mysql_free_result(res);
		memset(query,0,sizeof(query));
		sprintf(query,"delete from users where account='%s' and password='%s';",user.account,user.password);
		mysql_query(data,query);
		write(m_cd,"1",2);
	}
}

//私聊
void prichat(struct Infor outfor,int m_cd){
	int flag=0;
	int n=0;
	for(n;n<clinum;n++){
		if(strcmp(outfor.jiename,clin[n].name)==0){
			if(clin[n].cid!=0){
				flag=clin[n].cid;
				sprintf(outfor.jiename,"%s",outfor.faname);
				write(clin[n].cid,&outfor,sizeof(struct Infor));
				return;
			}
		}
	}
	if(flag==0){
		memset(&outfor,0,sizeof(outfor));
		outfor.flag=1;
		sprintf(outfor.buff,"对方不在线,请手动退出");
		write(m_cd,&outfor,sizeof(struct Infor));
	}
}

//群聊
void grochat(struct Infor outfor,int m_cd)
{
	int n=0;
	for(n;n<clinum;n++){
		if(clin[n].cid!=0&&clin[n].cid!=m_cd){
			sprintf(outfor.jiename,"%s",outfor.faname);
			write(clin[n].cid,&outfor,sizeof(struct Infor));
		}
	}
}

void login_ver(MYSQL *data){
	//创建TCP网络通信
	serfd=socket(AF_INET,SOCK_STREAM,0); 
    if(-1 == serfd) printf("创建serfd失败: %m\n"),exit(-1);
    printf("创建serfd %m\n");
	int count,flag=1;
	//开启多路IO
	setsockopt(serfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
	seraddr.sin_family=AF_INET; 
	seraddr.sin_port=htons(PORT); 
	seraddr.sin_addr.s_addr=inet_addr(IP); 
    //绑定
	int r = bind(serfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); 
    if(-1 == r) printf("绑定失败: %m\n"),close(serfd),exit(-1);
    printf("绑定 %m\n");
    //监听
	r = listen(serfd,10);
    if(-1 == r) printf("监听失败: %m\n"),close(serfd),exit(-1);
    printf("监听 %m\n");

	int pol,i;
	struct epoll_event ev;
	pol=epoll_create(1);
    //服务器的描述符号纳入监视范围
	ev.events=EPOLLIN;
	ev.data.fd=serfd;
    //把服务器的fd加入监视队列
	epoll_ctl(pol,EPOLL_CTL_ADD,serfd,&ev);
	struct epoll_event myepoll[10];
	struct User user;
	struct Infor infor;
	socklen_t len;
	struct sockaddr_in cli_addr[10]; 

	while(1){
		count=epoll_wait(pol,myepoll,10,-1);//非阻塞
		for(i=0;i<count;i++){
			if(myepoll[i].events==EPOLLIN){
				struct sockaddr_in cliaddr;
				len=sizeof(cliaddr);
				MYSQL_RES * res=NULL;
				MYSQL_ROW myrow;
				if(myepoll[i].data.fd==serfd){
					clifd=accept(serfd,(struct sockaddr*)&cliaddr,&len);
					clin[clinum].cid=clifd;
					clinum++;
					ev.events=EPOLLIN;
					ev.data.fd=clifd;
					cli_addr[clifd]=cliaddr;
					epoll_ctl(pol,EPOLL_CTL_ADD,clifd,&ev);
				}else{
					char buffer[1280] = {0};
					if((read_num=read(myepoll[i].data.fd,buffer,sizeof(buffer)))==0){
						for(int p=0;p<clinum;p++){
							if(myepoll[i].data.fd==clin[p].cid){
								clin[p].cid=0;
							}
						}
						printf("\n%d已下线\n",myepoll[i].data.fd);//用户已下线
						close(myepoll[i].data.fd);
						epoll_ctl(pol,EPOLL_CTL_DEL,myepoll[i].data.fd,NULL);
					}else{
						if(read_num==sizeof(struct User)){
							memcpy(&user, buffer, sizeof(struct User));
							switch(user.flag){
								case 1:
									regist(data,user,myepoll[i].data.fd,cli_addr[myepoll[i].data.fd]);
									break;
								case 2:
									login(data,user,myepoll[i].data.fd,cli_addr[myepoll[i].data.fd]);
									break;
								case 3:
									logout(data,user,myepoll[i].data.fd);
									break;
							}	
						}else if(read_num==sizeof(struct Infor)){
							memcpy(&infor, buffer, sizeof(struct Infor));
							switch(infor.flag){
								case 1:
									prichat(infor,myepoll[i].data.fd);
									break;
								case 2:
									grochat(infor,myepoll[i].data.fd);
									break;
						    }
					    }
				    }
			    }
		    }
	    }
    }
}